package com.ruoyi.game.core;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.game.constant.GameErrorCode;
import com.ruoyi.game.constant.GameRoomProperties;
import com.ruoyi.game.core.card.BasePoker;
import com.ruoyi.game.core.card.BasePokerLogic;
import com.ruoyi.game.core.exception.GameOpException;
import com.ruoyi.game.core.log.RoomLogContext;
import com.ruoyi.game.core.status.LiangZhuInfo;
import com.ruoyi.game.domain.GamePlayer;
import com.ruoyi.game.domain.GameRoom;
import com.ruoyi.game.domain.GameSet;
import com.ruoyi.game.service.IGamePlayerService;
import com.ruoyi.game.service.IGameRoomService;
import com.ruoyi.game.service.IGameSetService;
import com.ruoyi.game.websocket.handler.GameWebSocketHandler;
import com.ruoyi.game.websocket.message.dto.PlayerPosDTO;
import com.ruoyi.game.websocket.message.dto.RoomInfoDTO;
import com.ruoyi.game.websocket.message.packet.*;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

import static com.ruoyi.game.constant.GameErrorCode.*;

/**
 * 房间业务核心(桥接旧 SJRoom 逻辑)
 * 仅保留: 加入、准备、托管、出牌、广播
 */
@Slf4j
@Data
@RequiredArgsConstructor
public class GameRoomCore {

    private final Long roomId;                    // 数据库主键
    private final String roomKey;                 // 显示房号
    private final GameWebSocketHandler socketHandler;
    private final IGameRoomService gameRoomService;  // 注入 IGameRoomService
    private final IGameSetService gameSetService;    // 注入 IGameSetService
    private final IGamePlayerService gamePlayerService;    // 注入 IGamePlayerService
    private final GameRoomProperties gameRoomProperties;
    private final List<GameRoomPos> seats;
    private GameSetCore currentSet;
    private boolean inGame = false;
    private boolean robotRoom;              // 是否为机器人测试房间
    private boolean trusteeshipActive = false; // 是否激活托管
    private long setIdGenerator = 1;
    private volatile boolean running = true;     // 控制线程是否运行
    private Thread gameLoopThread;                // 游戏循环线程
    private long lastCleanupTime = System.currentTimeMillis(); // 上一次清理时间
    private long nextBotActionTime = 0;
    private long lastRoomStatePushTime = 0; // 上次房间状态广播时间

    // 构造函数增加是否为机器人房的标志
    public GameRoomCore(Long roomId, String roomKey, GameWebSocketHandler socketHandler, IGameRoomService gameRoomService,
                        IGameSetService gameSetService, IGamePlayerService gamePlayerService, GameRoomProperties gameRoomProperties, boolean robotRoom) {
        this.roomId = roomId;
        this.roomKey = roomKey;
        this.socketHandler = socketHandler;
        this.robotRoom = robotRoom;
        this.gameRoomService = gameRoomService;  // 注入 Service
        this.gameSetService = gameSetService;    // 注入 Service
        this.gamePlayerService = gamePlayerService;    // 注入 Service
        this.gameRoomProperties = gameRoomProperties;
        this.seats = new ArrayList<>(gameRoomProperties.getMaxPlayers());

        for (int i = 0; i < gameRoomProperties.getMaxPlayers(); i++) {
            GameRoomPos pos = new GameRoomPos(i);
            seats.add(pos);
        }

        // 启动房间的游戏循环线程
        startGameLoop();
    }

    // 启动房间的游戏循环线程
    private void startGameLoop() {
        gameLoopThread = new Thread(() -> {
            RoomLogContext.setRoomId(String.valueOf(roomId));
            try {
                loop(); // 游戏主循环
            } finally {
                RoomLogContext.clear();
            }
        }, "GameRoomLoop-" + roomId);

        gameLoopThread.start();
        log.info("房间 {} 的游戏循环线程已启动", roomId);
    }

    // 房间的主循环，定期更新房间状态
    private void loop() {
        while (running) {
            try {
                update(); // 更新房间状态，处理游戏逻辑
                Thread.sleep(gameRoomProperties.getUpdateInterval());
            } catch (InterruptedException e) {
                log.error("房间 {} 的游戏循环线程被中断", roomId);
                Thread.currentThread().interrupt();
                break; // 退出循环
            }
        }
    }

    public boolean isInGame() { return inGame; }

    /** 玩家进入并绑定 session，返回其座位号 */
    public int join(GamePlayer player) throws IOException {
        log.info("玩家 {} 尝试加入房间 {}，是否机器人={}", player.getId(), roomId, player.getIsRobot());

        // 2. 检查是否已经在本房间（防止重复加入）
        for (GameRoomPos pos : seats) {
            if (!pos.isEmpty() && pos.getPlayerId() == player.getId()) {
                log.warn("玩家 {} 已经在房间 {}，座位 {}", player.getId(), roomId, pos.getSeatIndex());
                WebSocketSession session = GameWebSocketHandler.getSessionFromPlayerId(player.getId());
                RoomInfoDTO roomInfo = this.buildRoomInfo();
                socketHandler.sendPacketToSession(session, WsPackets.roomInfo(new RoomInfoRsp(roomInfo)));
                return pos.getSeatIndex(); // ✅ 直接返回已有座位，不重复加入
            }
        }

        // 3. 找空座位
        GameRoomPos pos = findEmptySeat().orElse(null);
        if (pos == null) {
            log.error("房间 {} 已满，无法加入", roomId);
            WebSocketSession session = GameWebSocketHandler.getSessionFromPlayerId(player.getId());
            if (session != null) {
                socketHandler.sendError(session, ROOM_FULL, "房间已满，无法加入");
            }
            return -1;
        }

        // 4. 第一个进房间时，重置房间无人开始时间，避免被回收
        if (seats.stream().noneMatch(seat -> !seat.isEmpty())) {
            // 原来房间是空的，首次进人
            GameRoom gameRoom = gameRoomService.selectGameRoomById(roomId);
            gameRoom.setNobodyTime(farFutureDate());
            gameRoomService.updateGameRoom(gameRoom);
            log.info("房间 {} 首位玩家进入，重置 nobodyTime={}", roomId, gameRoom.getNobodyTime());
        }

        // 5. 填充座位信息
        WebSocketSession session = GameWebSocketHandler.getSessionFromPlayerId(player.getId());
        pos.setPlayerId(player.getId());
        pos.setReady(false);
        pos.setRobot(player.getIsRobot());
        pos.setName(player.getNickname());
        pos.setHeadImageUrl(player.getAvatar());
        pos.setSex(player.getSex());

        log.info("分配座位: seatIndex={}, 设置 ready=false", pos.getSeatIndex());

        // 6. 更新玩家绑定的房间ID
        GameWebSocketHandler.bindPlayerToRoom(player.getId(), roomId);

        // 7. 推送房间信息
        if (session != null) {
            RoomInfoDTO roomInfo = this.buildRoomInfo();
            socketHandler.sendPacketToSession(session, WsPackets.roomInfo(new RoomInfoRsp(roomInfo)));

            socketHandler.broadcastExclude(roomId,
                    WsPackets.roomUpdate(pos.getSeatIndex(), player!=null?player.getId():0, player!=null?player.getNickname():"", "join", true),
                    player.getId());
        }

        log.info("玩家 {} 成功加入房间 {}，座位 {}",  player.getId(), roomId, pos.getSeatIndex());

        if (player.getIsRobot()) {
            ready(player.getId(), true);
            log.info("机器人 {} 自动准备",  player.getId());
        }
        return pos.getSeatIndex();
    }

    /** 根据玩家ID踢出玩家 */
    public void kickOutPlayerByPlayerId(Long playerId, String title, String content) {
        GameRoomPos pos = findByPlayerId(playerId);
        if (pos != null) {
            kickOutPlayer(pos.getSeatIndex(), title, content);
        }
    }

    private static Date farFutureDate() {
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.YEAR, 100);
        return calendar.getTime();
    }

    public void endSet() {
        inGame = false;
        getCurrentSet().endSet();
    }

    /** 准备 / 取消准备 */
    public void ready(Long playerId, boolean ready) throws IOException {
        log.info("房间{} 玩家 {} 设置准备状态: {}", roomId, playerId, ready);

        GameRoomPos pos = findByPlayerId(playerId);
        if (pos == null) {
            log.error("房间{} 找不到玩家 {} 的座位", roomId, playerId);
            WebSocketSession session = GameWebSocketHandler.getSessionFromPlayerId(playerId);
            if (session != null) {
                socketHandler.sendError(session, ROOM_SEAT_NOT_FOUND, "座位不存在");
            }
            throw new GameOpException(GameErrorCode.ROOM_SEAT_NOT_FOUND, "找不到座位");
        }

        GamePlayer player = gamePlayerService.selectGamePlayerById(playerId);
        if (ready && !pos.isReady()) {
            // 只有第一次 ready 才扣除房卡
            if (!pos.isRobot()) {
                if (player == null) {
                    log.error("房间{} 玩家 {} 不存在", roomId, playerId);
                    WebSocketSession session = GameWebSocketHandler.getSessionFromPlayerId(playerId);
                    if (session != null) {
                        socketHandler.sendError(session, PLAYER_NOT_FOUND, "玩家不存在");
                    }
                    return;
                }
                Long roomCard = player.getRoomCard() != null ? player.getRoomCard() : 0L;
                if (roomCard < 1) {
                    log.warn("房间{} 玩家 {} 房卡不足，不能准备，踢出房间", roomId, playerId);
                    kickOutPlayer(pos.getSeatIndex(), "房卡不足，无法准备", "房卡不足，无法准备");
                    return;
                }
                player.setRoomCard(roomCard - 1);
                gamePlayerService.updateGamePlayer(player);
                log.info("房间{} 玩家 {} 扣除1张房卡成功，剩余={}", roomId, playerId, roomCard - 1);
            }
        }

        pos.setReady(ready);

        socketHandler.broadcastPacketToRoom(
                roomId,
                WsPackets.roomUpdate(pos.getSeatIndex(), playerId, player!=null?player.getNickname():"", "ready", ready)
        );

        boolean allReady = seats.stream()
                .allMatch(GameRoomPos::isReady);

        log.info("房间{} 检查所有人是否已准备: allReady={}, inGame={}", roomId, allReady, inGame);

        log.info("房间{} 玩家座位情况：", roomId);
        for (GameRoomPos seat : seats) {
            if (!seat.isEmpty()) {
                log.info("[ready] seatIndex={}, playerId={}, ready={}, trusteeship={}, robot={}",
                        seat.getSeatIndex(), seat.getPlayerId(), seat.isReady(), seat.isTrusteeship(), seat.isRobot());
            }
        }

        if (allReady && !isInGame()) {
            log.info("房间{} 所有人准备完毕，准备自动开始游戏", roomId);

            // 🔔 推送最新房间信息
            RoomInfoDTO roomInfo = buildRoomInfo();
            socketHandler.broadcastPacketToRoom(
                    roomId,
                    WsPackets.roomInfo(new RoomInfoRsp(roomInfo))
            );

            startSet();
        }

        log.info("房间{} 玩家 {} 的准备状态更新完毕", roomId, playerId);
    }

    private int readyPlayerCount() {
        return (int) seats.stream()
                .filter(pos -> !pos.isEmpty() && pos.isReady())
                .count();
    }

    // 踢出所有玩家并关闭他们的 WebSocket 会话
    public void kickOutPlayers() {
        this.getSeats().forEach(pos -> {
            if (!pos.isEmpty()) {
                // 调用复用的踢人方法
                kickOutPlayer(pos.getSeatIndex(), "房间已解散", "房间已解散");
            }
        });
        log.info("所有玩家已被踢出房间 {}", roomId);
    }

    public void kickOutPlayer(Long playerId, String message, String reason) {
        GameRoomPos pos = findByPlayerId(playerId);
        if (pos == null) {
            log.warn("房间{} 踢出失败，找不到玩家 {} 的座位", roomId, playerId);
            return;
        }
        WebSocketSession session = GameWebSocketHandler.getSessionFromPlayerId(playerId);

        // 广播整个房间踢出通知
        if (session != null) {
            socketHandler.broadcastPacketToRoom(roomId, WsPackets.roomKickOut(playerId, roomId, message, reason));
            socketHandler.closeSession(session, CloseStatus.NORMAL);  // 关闭 WebSocket 会话
        }

        log.info("房间{} 踢出 playerId={} 原因={}", roomId, playerId, reason);

        // 清空座位
        pos.clear();
    }

    public void kickOutPlayer(int seatIndex, String message, String reason) {
        if (seatIndex < 0 || seatIndex >= seats.size()) {
            log.warn("房间{} 无效的座位索引 seatIndex={}", roomId, seatIndex);
            return;
        }
        GameRoomPos pos = seats.get(seatIndex);
        if (pos == null || pos.isEmpty()) {
            log.warn("房间{} 踢出失败，座位为空 seatIndex={}", roomId, seatIndex);
            return;
        }
        kickOutPlayer(pos.getPlayerId(), message, reason);
    }


    /** 玩家离开房间 */
    public void leave(Long playerId) {
        log.info("玩家 {} 请求离开房间 {}", playerId, roomId);

        GameRoomPos pos = findByPlayerId(playerId);
        if (pos == null) {
            log.warn("玩家 {} 不在房间 {} 内", playerId, roomId);
            WebSocketSession session = GameWebSocketHandler.getSessionFromPlayerId(playerId);
            if (session != null) {
                socketHandler.sendError(session, ROOM_SEAT_NOT_FOUND, "座位不存在，无法离开");
            }
            return;
        }

        int seatIndex = pos.getSeatIndex();
        // 1. 结束牌局
        endSet();

        // 2. 清除座位
        pos.clear();

        // 3. 设置noby时间
        boolean allEmpty = seats.stream().allMatch(GameRoomPos::isEmpty);
        if (allEmpty) {
            GameRoom gameRoom = gameRoomService.selectGameRoomById(roomId);
            gameRoom.setNobodyTime(new Date());
            gameRoomService.updateGameRoom(gameRoom);
            log.info("房间 {} 所有人离开，nobodyTime={}", roomId, gameRoom.getNobodyTime());
        }

        // 4. 广播座位更新
        GamePlayer player = gamePlayerService.selectGamePlayerById(playerId);
        socketHandler.broadcastPacketToRoom(roomId, WsPackets.roomUpdate(seatIndex, playerId, player!=null?player.getNickname():"",  "leave", true));

        // 5. 清理和关闭连接
        WebSocketSession session = GameWebSocketHandler.getSessionFromPlayerId(playerId);
        socketHandler.closeSession(session, CloseStatus.NORMAL);
        log.info("玩家 {} 离开房间 {}，座位 {}", playerId, roomId, seatIndex);
    }

    /** 手动托管切换 */
    public void trusteeship(Long playerId, boolean enable) throws IOException {
        log.info("房间{} 玩家 {} 设置托管={}", roomId, playerId, enable);

        GameRoomPos pos = findByPlayerId(playerId);
        pos.setTrusteeship(enable);

        socketHandler.broadcastPacketToRoom(roomId, new WsPacket<>("TRUSTEESHIP", WsPackets.trusteeship(pos.getSeatIndex(), enable)));

        log.info("房间{} 广播玩家 {} 的托管状态，seat={}", roomId, playerId, pos.getSeatIndex());
    }

    public GameRoomPos findByPlayerId(Long playerId) {
        return seats.stream()
                .filter(s -> s.getPlayerId() != 0 && s.getPlayerId() == playerId)
                .findFirst()
                .orElse(null);
    }

    private Optional<GameRoomPos> findEmptySeat() {
        return seats.stream().filter(GameRoomPos::isEmpty).findFirst();
    }

    public boolean containsPlayer(Long playerId) {
        return seats.stream().anyMatch(pos -> playerId != null && playerId.equals(pos.getPlayerId()));
    }

    public void startNewSet() {
        GameSet gameSet = new GameSet();
        gameSet.setRoomId(this.roomId);
        gameSet.setSetIndex(gameSetService.getNextSetIndex(this.roomId)); // 如需手动管理
        gameSet.setStartTime(new Date());
        gameSet.setCreateTime(new Date());
        gameSetService.insertGameSet(gameSet); // 插入后自动回填主键 ID

        long newSetId = gameSet.getId(); // 主键
        this.currentSet = new GameSetCore(newSetId, this, gameSetService, gamePlayerService, gameRoomProperties);
    }

    public RoomInfoDTO buildRoomInfo() {
        List<PlayerPosDTO> playerList = seats.stream()
                .filter(p -> !p.isEmpty())
                .map(pos -> {
                    PlayerPosDTO dto = new PlayerPosDTO();
                    dto.setSeatIndex(pos.getSeatIndex());
                    dto.setPlayerId(pos.getPlayerId());
                    dto.setName(pos.getName());
                    dto.setHeadImageUrl(pos.getHeadImageUrl());
                    dto.setSex(pos.getSex());
                    dto.setPoint(pos.getScore());
                    dto.setReady(pos.isReady());
                    dto.setTrusteeship(pos.isTrusteeship());
                    dto.setOffline(false);
                    dto.setPlayed(false);
                    dto.setSportsPoint(null);
                    dto.setClubName(null);
                    dto.setUpLevelName(null);
                    return dto;
                })
                .collect(Collectors.toList());

        long setId = currentSet != null ? currentSet.getSetId() : 0L;
        int dealerSeat = currentSet != null ? currentSet.getDealerPos() : -1;

        return new RoomInfoDTO(roomId, setId, playerList, dealerSeat, 0);
    }

    public void startSet() {
        log.info("房间 {} 准备启动游戏", roomId);
        if (inGame) {
            log.warn("房间{} 游戏已在进行中，跳过启动", roomId);
            throw new GameOpException(GameErrorCode.GAME_ALREADY_STARTED, "游戏已在进行中！");
        }

        long readyCount = seats.stream()
                .filter(pos -> !pos.isEmpty())
                .count();

        if (readyCount < gameRoomProperties.getMaxPlayers()) {
            log.warn("房间{} 启动失败：房间人数不足{}人", roomId, gameRoomProperties.getMaxPlayers());
            // ✅ 广播一条统一的错误消息给房间所有玩家
            socketHandler.broadcastPacketToRoom(
                    roomId,
                    WsPackets.error(GameErrorCode.ROOM_PLAYER_NOT_ENOUGH, "房间人数不足，无法开始")
            );
            throw new GameOpException(GameErrorCode.ROOM_JOIN_FAILED, "房间人数不足"+gameRoomProperties.getMaxPlayers()+"人，无法开始");
        }

        inGame = true;
        startNewSet();

        // 启动托管处理
        setTrusteeshipActive(true);

        log.info("房间 {} 启动新一局 setId={}", roomId, currentSet.getSetId());
        currentSet.update(0);
    }

    public void setTrusteeshipActive(boolean active) {
        this.trusteeshipActive = active;
    }

    public void  update() {
        long now = System.currentTimeMillis();
        // 如果当前对局存在并且未结束，正常更新对局
        if (currentSet != null && !SetState.ENDED_STATE.equals(currentSet.getState())) {
            currentSet.update((int) (System.currentTimeMillis() / 1000)); // 更新当前对局
        }

        // ➤ 推送房间状态封包（仅在未开局阶段）
        if (currentSet == null || SetState.ENDED_STATE.equals(currentSet.getState())) {
            long nowSec = System.currentTimeMillis() / 1000;
            if (nowSec - lastRoomStatePushTime >= gameRoomProperties.getRoomStatePushInterval()) {
                lastRoomStatePushTime = nowSec;

                List<PlayerStateInfo> seatStates = new ArrayList<>();
                for (int i = 0; i < seats.size(); i++) {
                    GameRoomPos pos = seats.get(i);
                    PlayerStateInfo info = new PlayerStateInfo();
                    info.setSeatIndex(i);
                    info.setOnline(GameWebSocketHandler.isPlayerOnlineInRoom(pos.getPlayerId(), getRoomId()) || pos.isRobot());
                    info.setPlayerName(pos.getName());
                    info.setRobot(pos.isRobot());
                    info.setReady(pos.isReady());
                    seatStates.add(info);
                }

                RoomStatePush push = new RoomStatePush();
                push.setRoomId(roomId);
                push.setSeatStates(seatStates);

                socketHandler.broadcastPacketToRoom(roomId, WsPackets.roomStatePush(push));
                log.info("房间 {} 未开局状态，广播 RoomStatePush 封包", roomId);
            }
        }

        // 如果对局已结束，且托管激活，检查是否已过30秒
        if (currentSet != null && SetState.ENDED_STATE.equals(currentSet.getState()) && trusteeshipActive) {
            if (isEndSetWithWaitingTime()) {
                int startSeat = currentSet.getLastWinnerOrDealer();
                int total = seats.size();
                for (int i = 0; i < total; i++) {
                    int seatIndex = (startSeat + i) % total;
                    GameRoomPos pos = seats.get(seatIndex);

                    if (!pos.isTrusteeship() && !pos.isRobot()) continue;
                    // 托管玩家和机器人自动准备
                    if (pos.isRobot() && !pos.isReady()) {
                        try {
                            log.info("房间{} seat={} 是机器人，自动准备", roomId, seatIndex);
                            ready(pos.getPlayerId(), true);  // 机器人自动准备
                        } catch (IOException e) {
                            log.error("房间{} 机器人准备失败，seat={}", roomId, seatIndex, e);
                        }
                    }
                }
            }
        }
        else {
            handleAllTrusteeship();
        }

        // 定时清理无人房间（每5分钟一次）
        if (now - lastCleanupTime >= gameRoomProperties.getCleanupIntervalMs()) {
            gameRoomService.deleteExpiredWaitingRooms();
            lastCleanupTime = now;
        }
    }

    private boolean isEndSetWithWaitingTime() {
        // 检查当前局是否结束且已过30秒
        if (currentSet.getState().equals(SetState.ENDED_STATE)) {
            long elapsedTime = System.currentTimeMillis() - currentSet.getEndTime().getTime();
            return elapsedTime >= gameRoomProperties.getSetEndWaitingTime();  // 如果当前局已经结束并且超过30秒
        }
        return false;
    }

    /**
     * 将客户端传入的 PKOpTypeReq，映射为真实的 PKOpType
     */
    public static PKOpType mapOpType(int seatIndex,  GameSetCore currentSet, PKOpTypeReq reqType, List<Integer> cards) {
        if (reqType == PKOpTypeReq.NONE) {
            return PKOpType.NONE;
        }

        if (reqType == PKOpTypeReq.OUT_CARD) {
            if (currentSet.getCurrentRound() != null && !currentSet.getCurrentRound().isFirstOp()) {
                return PKOpType.FOLLOW;
            } else {
                return PKOpType.OUT_CARD;
            }
        }

        if (reqType == PKOpTypeReq.KOU_DI) {
            return PKOpType.KOU_DI;
        }

        if (reqType == PKOpTypeReq.HAN_DA) {
            return PKOpType.HAN_DA;
        }

        if (reqType == PKOpTypeReq.DA_DU) {
            return PKOpType.DA_DU;
        }

        if (reqType == PKOpTypeReq.LIANG_ZHU) {
            if (currentSet.hasLiangZhu()) {
                // 已经有人亮主了
                LiangZhuInfo info = currentSet.getLiangZhuInfo();
                int oldColor = info.getZhuColor();
                int newColor = BasePoker.calcMainColor(cards, currentSet.getZhuValue());

                if (info.getSeatIndex() != seatIndex) {
                    // 不是自己亮的，反主
                    return PKOpType.FAN_ZHU;
                } else {
                    // 是自己亮的，判断自保 or 自反
                    if (newColor == oldColor) {
                        return PKOpType.ZI_BAO;
                    } else {
                        if (BasePoker.isDaWang(cards.get(0)) || BasePoker.isXiaoWang(cards.get(0))) {
                            return PKOpType.ZI_BAO;
                        }
                        return PKOpType.ZI_FAN;
                    }
                }
            } else {
                // 无人亮主，正常亮一张/两张
                if (cards.size() == 1) {
                    return PKOpType.LIANG_ZHU1;
                } else if (cards.size() == 2 || cards.size() == 3) {
                    return PKOpType.LIANG_ZHU2;
                } else {
                    throw new GameOpException(GameErrorCode.INVALID_CARD, "亮主时必须出1或2张牌！");
                }
            }
        }

        throw new GameOpException(GameErrorCode.INVALID_CARD, "不支持的操作类型！" + reqType);
    }

    public void opPlayerCard(Long playerId, PKOpTypeReq reqOpType, List<Integer> cards) {
        WebSocketSession session = GameWebSocketHandler.getSessionFromPlayerId(playerId);
        if (currentSet == null) {
            socketHandler.sendError(session, GameErrorCode.GAME_NOT_FOUND, "未开始游戏");
            log.warn("房间{} 当前没有进行中的对局，忽略玩家 {} 的出牌", roomId, playerId);
            return;
        }

        GameRoomPos roomPos = findByPlayerId(playerId);
        if (roomPos == null) {
            socketHandler.sendError(session, GameErrorCode.PLAYER_NOT_FOUND, "玩家不在该房间");
            log.warn("房间{} 未找到玩家 {}, 无法出牌", roomId, playerId);
            return;
        }

        int seatIndex = roomPos.getSeatIndex();
        int roundId = currentSet.getCurrentRound() != null ? currentSet.getCurrentRound().getRoundId() : -1;

        log.info("房间{} 玩家 {} 请求出牌: setId={}, roundId={}, reqOpType={}, cards={}", roomId, playerId, currentSet.getSetId(), roundId, reqOpType, cards);

        // ✅ 映射 PKOpTypeReq -> PKOpType
        PKOpType realOpType;
        try {
            realOpType = mapOpType(seatIndex, currentSet, reqOpType, cards);
        } catch (Exception e) {
            socketHandler.sendError(session, GameErrorCode.INVALID_OP, "无效的出牌操作");
            log.warn("房间{} 玩家 {} 出牌映射失败，reqOpType={}, cards={}", roomId, playerId, reqOpType, cards);
            return;
        }

        log.info("房间{} 玩家 {} 映射后实际操作 realOpType={}",roomId, playerId, realOpType);
        boolean success = currentSet.handlePlayerOp(seatIndex, roundId, realOpType, cards);

        log.info("房间{} 玩家 {} 出牌成功，广播完成", roomId, playerId);
    }


    /** 遍历并处理房间内所有托管玩家的操作 */
    public void handleAllTrusteeship() {
        if (currentSet == null) return;

        int startSeat = currentSet.getLastWinnerOrDealer();
        int total = seats.size();
        long now = System.currentTimeMillis();
        long delayMs = gameRoomProperties.getRobotCardoutThinkTime();
        if (nextBotActionTime == 0) {
            nextBotActionTime = now + delayMs; // 初始化起点
        }
//        log.info("房间{} [托管/机器人] 从座位 {} 开始依次处理托管玩家...", roomId, startSeat);

        for (int i = 0; i < total; i++) {
            int seatIndex = (startSeat + i) % total;
            GameRoomPos pos = seats.get(seatIndex);

            if (!pos.isTrusteeship() && !pos.isRobot()) continue;

            // 若还未到轮到该机器人的时间，则跳过
            if (now < nextBotActionTime) {
                log.info("房间{} seat={} 等待下一机器人出牌时间：{} > {}", roomId, seatIndex, nextBotActionTime, now);
                return; // ⚠️ 注意：return，而不是 continue，实现串行出牌
            }

            log.info("房间{} seat={} 机器人/托管执行出牌", roomId, seatIndex);
            boolean ok = currentSet.handleTrusteeship(seatIndex);

            // 设置下一次允许执行的时间（链式延迟）
            if (ok)
                nextBotActionTime = now + delayMs;
        }
    }

    /** 处理指定座位的托管操作（仅由机器人或托管玩家触发） */
    public boolean handleTrusteeship(int seatIndex) {
        GameRoomPos pos = seats.get(seatIndex);
        if (!pos.isTrusteeship() && !pos.isRobot()) {
            log.info("房间{} [托管] 座位 {} 非托管状态并且不是机器人，跳过", roomId, seatIndex);
            return true;
        }
        if (currentSet == null) {
            log.warn("房间{} [托管] 当前没有有效对局，无法处理托管操作", roomId);
            return true;
        }

        log.info("房间{} [托管] 执行座位 {} 的托管操作（机器人={}）", roomId, seatIndex, pos.isRobot());
        return currentSet.handleTrusteeship(seatIndex);
    }

    public void mockShuffleAndDeal(WebSocketSession session) {
        try {
            int playerNum = 5; // 固定5人
            List<Integer> fixedDeck = BasePokerLogic.getFixedPokerList(); // 你的测试牌组

            // 分配牌
            Map<Integer, List<Integer>> handCardsMap = new HashMap<>();
            for (int i = 0; i < playerNum; i++) {
                handCardsMap.put(i, new ArrayList<>());
            }

            for (int i = 0; i < fixedDeck.size() - 8; i++) {
                int seat = i % playerNum;
                handCardsMap.get(seat).add(fixedDeck.get(i));
            }

            List<Integer> kouPaiList = fixedDeck.subList(fixedDeck.size() - 8, fixedDeck.size()); // 底牌

            // 封包推送
            WsPacket<StartSetPush> packet = WsPackets.startSet(
                    1,           // setId 测试用固定1
                    0,           // dealerSeatIndex 默认seat0
                    handCardsMap,
                    kouPaiList,
                    fixedDeck,
                    30           // callZhu限时秒数
            );

            socketHandler.sendPacketToSession(session, packet);

            log.info("[TEST_DEAL] 测试发牌封包已发送");
        } catch (Exception e) {
            log.error("[TEST_DEAL] 测试发牌失败", e);
            socketHandler.sendError(session, GameErrorCode.DEAL_ERROR, "测试发牌失败");
        }
    }

}
